home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / mail / maillist < prev    next >
Encoding:
Korn shell script  |  1997-08-26  |  21.4 KB  |  677 lines

  1. #!/bin/ksh
  2. # @(#) maillist.ksh 1.4 97/02/19
  3. # 92/09/27 john h. dubois iii (john@armory.com)
  4. # 92/10/13 Added dellist capability.
  5. # 92/10/15 Check for whether user is already on list.
  6. # 92/10/17 Allow multiple users on cmd line.
  7. # 92/11/09 Added help.
  8. # 93/07/13 Do not create list- if no changes are made.
  9. # 93/07/17 Store lists with multiple addresses per line.  Added -c option.
  10. # 94/01/10 Make default for LISTS be home dir, not current dir.
  11. # 94/04/05 Added A and R options.
  12. # 94/04/23 Use .maillist file.
  13. # 94/07/28 Let quoted patterns be used for mailing list names.
  14. # 94/08/15 Give better error messages if list does not exist or isn't readable.
  15. # 95/04/13 Make -[dD] ("delete") be the same as -[rR] ("remove")
  16. # 95/10/16 Added fi options.  Don't require mailing list name if the user has
  17. #          only one mailing list.  Let % by a synonym for *.  Improved help.
  18. # 96/01/26 Report full path to alias file on error.
  19. #          Make list file be relative to current dir if it contains a /
  20. # 96/01/29 Let options be grouped together.  Added [qs] options.
  21. # 96/02/16 Made modifiers work again; made c option work again.
  22. # 97/02/19 Ignore case in addresses.
  23.  
  24. alias istrue="test 0 -ne"
  25. alias isfalse="test 0 -eq"
  26. rcfile=.maillist
  27.  
  28. # Usage: ReadList <array> <listname>
  29. # The comma/whitespace separated words in file <listname> are read, sorted,
  30. # and put into array <array>
  31. function ReadList {
  32.     typeset IFS="$IFS," arr=$1 list=$2
  33.  
  34.     if [ ! -f "$list" ]; then
  35.     print -u2 "$lngname: $list: No such mailing list in $PWD."
  36.     return 1
  37.     fi
  38.     if [ ! -r "$list" ]; then
  39.     print -u2 "$lngname: Cannot read list datafile '$PWD/$list'."
  40.     return 1
  41.     fi
  42.     set -s -A $arr -- $(<$list)
  43. }
  44.  
  45. # Usage: IsOnList user-name
  46. # Returns success if user-name is in any element of global MList[]
  47. function IsOnList {
  48.     typeset -i nelem=${#MList[*]} i=0
  49.     typeset -l user=$1 listelem
  50.  
  51.     while [ i -lt nelem ]; do
  52.     listelem=${MList[i]}
  53.     [ "$user" = "$listelem" ] && return 0
  54.     let i+=1
  55.     done
  56.     return 1
  57. }
  58.  
  59. # Print words passed as args with max line length
  60. # Usage: PrintMaxLines <maxlinelength> <sep> <end> word ...
  61. # <sep> is used to separate words on a line.
  62. # <end> is put at the end of each line except the last.
  63. function PrintMaxLines {
  64.     typeset maxline=$1 sep=$2 end=$3 outline= word
  65.     typeset -i printedline=0 extra=${#sep}+${#end}
  66.  
  67.     shift 3
  68.     for word; do
  69.     if [[ $(( ${#outline}+${#word}+$extra )) -gt $maxline ]]; then
  70.         isfalse printedline && printedline=1 || print -r "$end"
  71.         print -nr "$outline"
  72.         outline=$word
  73.     else
  74.         [ -z "$outline" ] && outline=$word || outline="$outline$sep$word"
  75.     fi
  76.     done
  77.     # print final line if it hasn't been printed yet
  78.     istrue printedline && print -r "$end"
  79.     print -r "$outline"
  80. }
  81.  
  82. # Usage: WriteList <listname>
  83. # Writes mailing list in MList[] to file <listname>
  84. function WriteList {
  85.     cp "$list" "$list-"
  86.     PrintMaxLines 79 ", " "," "${MList[@]}" > $list ||
  87.     print -u2 "$lngname: List write failed."
  88. }
  89.  
  90. # Usage: iInd <arrayname> <varname> <value> <nsearch> <firstelem>
  91. # Sets <varname> to the index of the first element of <arrayname>
  92. # that has value <value>.  Case is ignored.
  93. # If the value is not found, <varname> is not set and 1 is returned;
  94. # if it is, 0 is returned.
  95. # If <nsearch> is given, the first <nsearch> elements of the array are
  96. # searched, with only nonempty elements counted.
  97. # If not, the first n nonempty elements are searched (up to 1024),
  98. # where n is the number of elements in the array.
  99. # If a fourth argument is given, it is the index to start with; the search
  100. # continues for <nsearch> elements.
  101. function iInd {
  102.     typeset -i NElem ElemNum=${5:-0} NumNonNull=0
  103.     typeset Arr=$1 var=$2
  104.     typeset -l ElemVal Val=$3
  105.  
  106.     [ $# -ge 4 ] && NElem=$4 || eval NElem=\${#$Arr[*]}
  107.     while [ ElemNum -le 1023 -a NumNonNull -lt NElem ]; do
  108.     eval ElemVal=\"\${$Arr[ElemNum]}\" 
  109.     if [ "$Val" = "$ElemVal" ]; then
  110.         eval $var=\$ElemNum
  111.         return 0
  112.     fi
  113.     [ -n "$ElemVal" ] && let NumNonNull+=1
  114.     let ElemNum+=1
  115.     done
  116.     return -1
  117. }
  118.  
  119. # Usage: ChangeHosts listname old-host new-host
  120. # If old-host exists in listname, remove it and add new-addr to listname.
  121. # will need to copy much of DoUsers into this...
  122. #function ChangeList {
  123. #    typeset list=$1 old new
  124. #
  125. #    while [ $# -gt 1 ]; do
  126. #    old=$2 new=$3
  127. #    if DoUsers 0 "$list" "$old"; then
  128. #        DoUsers 1 "$list" "$new"
  129. #    else
  130. #        $Quiet || print -u2 "$lngname: $new not added to list $list."
  131. #    fi
  132. #    shift 2
  133. #    done
  134. #}
  135.  
  136. # Usage: ChangeList listname old-addr new-addr [old-addr new-addr ...]
  137. # If old-addr exists in listname, remove it and add new-addr to listname.
  138. function ChangeList {
  139.     typeset list=$1 old new
  140.  
  141.     while [ $# -gt 1 ]; do
  142.     old=$2 new=$3
  143.     if DoUsers 0 "$list" "$old"; then
  144.         DoUsers 1 "$list" "$new"
  145.     else
  146.         $Quiet || print -u2 "$lngname: $new not added to list $list."
  147.     fi
  148.     shift 2
  149.     done
  150. }
  151.  
  152. # Usage: DoUsers 0|1 <listname> user ...
  153. # Use 0 to remove a user, 1 to add a user
  154. # Returns 0 on complete success, 1 if a user was already on a list to be
  155. # added to or not on a list to be remove from or if any other error occurs.
  156. function DoUsers {
  157.     typeset -i add=$1
  158.     typeset list=$2
  159.     typeset OnList OffList User
  160.     typeset -i OnInd=0 OffInd=0 UInd err=0
  161.  
  162.     shift 2
  163.  
  164.     ReadList MList $list || return 1
  165.     for User; do
  166.     if IsOnList "$User"; then
  167.         OnList[OnInd]=$User
  168.         let OnInd+=1
  169.     else
  170.         OffList[OffInd]=$User
  171.         let OffInd+=1
  172.     fi
  173.     done
  174.     if istrue add; then
  175.     if [ OnInd -gt 0 ]; then
  176.         echo "Warning: already on list \"$list\": ${OnList[*]}."
  177.         err=1
  178.     fi
  179.     if [ OffInd -gt 0 ]; then
  180.         set -s -A MList -- "${MList[@]}" "${OffList[@]}"
  181.         WriteList $list
  182.         echo "Added to list $list: ${OffList[*]}"
  183.     fi
  184.     else
  185.     if [ OffInd -gt 0 ]; then
  186.         $Quiet || echo "Warning: not on list \"$list\": ${OffList[*]}."
  187.         err=1
  188.     fi
  189.     if [ OnInd -gt 0 ]; then
  190.         for User in "${OnList[@]}"; do
  191.         if iInd MList UInd "$User"; then
  192.             unset MList[UInd]
  193.         else
  194.             print -u2 "$lngname: Failed to find index of $User."
  195.             err=1
  196.         fi
  197.         done
  198.         WriteList $list
  199.         echo "Removed from list $list: ${OnList[*]}"
  200.     fi
  201.     fi
  202.     return $err
  203. }
  204.  
  205. # Usage: fix <listname>
  206. function fix {
  207.     ReadList MList "$1" || return 1
  208.     WriteList "$1"
  209.     chmod a+r "$1"
  210. }
  211.  
  212. # Usage: findPat <user-pattern> <listname>
  213. function findPat {
  214.     typeset list=$1 found=false
  215.     typeset -l userPat=$2 user
  216.  
  217.     ReadList MList "$list" || return 1
  218.     for user in "${MList[@]}"; do
  219.     if eval [[ '"$user"' = "*$userPat*" ]]; then
  220.         $found || { print -n "$list:"; found=true; }
  221.         print -n " $user"
  222.     fi
  223.     done
  224.     $found && print ""
  225.     return 0
  226. }
  227.  
  228. # Usage: initialize <listname>
  229. function initialize {
  230.     if [ ! -f "$1" ]; then
  231.     touch -- "$1" ||  {
  232.         print -u2 "Could not create list file '$1'".
  233.         exit 1
  234.     }
  235.     chmod 744 "$1"
  236.     print -u2 "Created list file '$1'."
  237.     else
  238.     print -u2 "Checking list file '$1'."
  239.     fix "$1"
  240.     fi
  241. }
  242.  
  243. function printLists {
  244.     [ numLists -gt 1 ] && print "$list:"
  245.     print -- "$(<$list)"
  246. }
  247.  
  248. function doArgs {
  249.     typeset opt Users ListArr OFS add list Args OnlyOne Lists act modifier
  250.     typeset lNames line
  251.     typeset -i i j MinArgs
  252.  
  253.     while getopts hfipqaArRcdDslun opt; do
  254.     case $opt in
  255.     h)
  256.         echo \
  257. "$lngname: Manipulate mailing lists.
  258.      $lngname prints and modifies \"included\" mailing lists.  An included
  259. mailing list is one which exists as a file containing a list of recipients
  260. which is read each time mail for the list is received.  The other type of
  261. mailing list is an internal list; these mailing lists are specified in the
  262. MMDF alias files and become part of the MMDF database when dbmbuild(ADM) is
  263. run.  Included mailing lists are typically used to allow automated mailing list
  264. management tools (like majordomo) to manipulate mailing lists, to allow mailing
  265. lists to be easily updated (since dbmbuild does not have to be run after each
  266. modification), and to allow users (who are not allowed to run dbmbuild) to
  267. maintain their own mailing lists.  This utility is intended to facilitate the
  268. use of included mailing lists for the latter two purposes.
  269.      Note that both internal and included mailing lists are only needed (and
  270. should only be used) when it is desirable for multiple users to be able to mail
  271. to a list address.  If a list is to be created for use by only one user, a
  272. mailing list read by the user's mail sending utility should be used instead.
  273.      See the tables(F) man page for a description of how to configure the MMDF
  274. system to use an included mailing list.  This must be done by the system
  275. administrator, after the list file has been created.  If the list is configured
  276. into MMDF first, any mail sent to it before the file exists or before the file
  277. is made accessible to the deliver daemon will result in an error, with mail
  278. sent to the system administrator.  You should also ensure that all directories
  279. in the path up to the list file are publicly executable.  That can be done with
  280. the -i or -f option.  $lngname only modifies the contents mailing list files
  281. (adding and removing recipients); it does not do any MMDF configuration.
  282.      For simplicity, $lngname maintains mailing lists in a file with the same
  283. name as that of the list.  The file must already exist, unless -i is being
  284. used.  Since it will be read by the deliver daemon, it must be readable and
  285. accessible by \"other\".  You can initially create the file either by using the
  286. -i option, or with the commands:
  287. touch <listname>
  288. chmod a+r <listname>
  289. where <listname> is the name of the mailing list file.
  290.      If the filename contains a '/' character, it is searched for relative to
  291. the current directory.  If it does not, the file is searched for in the
  292. directory given by the environment variable \"MAILLISTS\".  MAILLISTS can also
  293. be set by creating a file named $rcfile in your home directory and
  294. putting in it a line of the form:
  295. MAILLISTS=maillist-dir
  296. where maillist-dir is the mailing list directory.  If MAILLISTS is not set
  297. either in the environment or in $rcfile, it defaults to your home directory.
  298. The MAILLISTS environment variable takes precedence over the value set in
  299. $rcfile.
  300.      Mailing list files should contain addresses separated by commas and
  301. optional whitespace.  The initial list of recipients can be put in the file
  302. manually, and it can later be edited manually, so long as this rule is
  303. observed.  $lngname does not require that names be separated by commas, but the
  304. deliver daemon does.  If the mailing list is initially created with names
  305. separated by whitespace only, $lngname -k should be run on the file before it
  306. is used, to put it in the format that the deliver daemon expects.  Alternately,
  307. all operations on the file can be done through $lngname.
  308.      When $lngname is run, a copy of the list being operated on as it currently
  309. exists is saved in listname- (a file with the same name as the list but with a
  310. '-' attached).  Mailing list names that end in '-' are ignored, so * can be
  311. used in a directory that contains only mailing lists to specify all of them. 
  312. It should not be used if the directory contains any non-dotfiles that are not
  313. mailing list files.  Also, shell patterns (like *) can be used from any
  314. directory if quoted to protect them from initial expansion by the shell; they
  315. will be expanded in the mailing list directory.  The character '%' can be used
  316. as a synonym for *, with the advantage that it does not have to be quoted.
  317. $Usage
  318.      To make this program easier to use in cases where a user is to be added or
  319. removed from a number of mailing lists, or where a number of users are to be
  320. added/removed from a mailing list, the add and remove functions can each be
  321. given in one of two ways.  The argument is a list of users or mailing lists
  322. separated by commas; further arguments are a list of users or mailing lists
  323. (whichever was not given as the first argument) separated by whitespace.  If
  324. a pattern (like *) that will be expanded into a list of mailing list files is
  325. used, the first form should be used, since the pattern will expand to a list 
  326. of mailing list files separated by whitespace.  If a command line substitution
  327. that will put user names on the command line is used, the second form should be
  328. used for the same reason.  If all parameters will be explicitly given on the
  329. command line, either form may be used.
  330. Exactly one of the following options must be given.  If arguments are given
  331. after an option, a space must separate the option from the arguments.
  332. Options:
  333. -h: Print this help information.
  334. -f listname [listname ...]: Fix the mailing list files: read the files and
  335.     rewrite them in the format the deliver daemon expects, and set their
  336.     permissions.
  337. -i listname [listname ...]: Initialize mailing list files.  Like -f, except
  338.     that any files that do not exist are created (as empty files).  If MAILLIST
  339.     is set, the files are created in the directory it specifies; if not, the
  340.     files are created in the user's home directory.  If the directory does not
  341.     exist, it will be created.
  342. -p [listname ...]  Print the specified mailing lists.  If not listnames are
  343.     given, all lists are printed.  If more than one mailing list is printed,
  344.     its name is printed first.
  345. -q: When removing a user from lists, do not complain about lists the user was
  346.     not on.  When changing a user address, do not warn about lists the user
  347.     was not added to.
  348. -[ar] user[,user...] listname [listname ...]
  349. -[AR] listname[,listname...] user [user ...]
  350.     Add/remove the specified users to/from the mailing list(s).  -d and -D are
  351.     identical to -r and -R.  If the letter 'u' or 'l' follows any of these
  352.     options (e.g. -au or -al), then the list of users or mailing list names
  353.     respectively is read from the standard input and should not be given on the
  354.     command line.  Multiple user or mailing list names may be given on input
  355.     lines.
  356. -s user-pattern [listname ...]: Print list names that user-pattern
  357.     occurs in, followed by the users who matched the pattern.  user-pattern is
  358.     an unanchored shell pattern.  If no listnames are given, % is used.
  359. -n old-hostname new-hostname [listname ...]
  360.     Change every matching hostname found in the recipient names on the given
  361.     mailing lists to new-hostname.  old-hostname must match the entire hostname
  362.     part of an address.  The only forms of address understood are user@hostname
  363.     and hostname!user.  (The hostname will actually also match certain parts of
  364.     a UUCP or source route, since this option looks for a match on a hostname
  365.     after the last @ in an address and before the first !, but this should not
  366.     be relied upon).  If no listnames are given, all lists are acted on.
  367.     Note: -n is not yet implemented.
  368. -c current-address new-address [listname ...]
  369.     Change the email address of the specified user on the mailing list(s).
  370.     Only one user can be changed per invokation.  The current subscription
  371.     address should be given first, then the new subscription address.  If
  372.     current-address does not exist in a list, new-address is not added to it.
  373.     For example, to change the subscription address of user@foo to user@bar
  374.     on mylist, use: $lngname -c user@foo user@bar mylist
  375.     If -cu is given instead of -c, pairs of addresses separated by whitespace
  376.     are read from the standard input.  For each line read, the first address
  377.     given on the line is taken to be a current address and the second address
  378.     is taken to be a new address and they are used to do a replacement as
  379.     described above.  In this case, all arguments are taken to be listnames.
  380.     If no listnames are given, all lists are acted on.
  381. For the f, p, a, r, and c options only, if the user has exactly one mailing
  382. list, and MAILLISTS is set, and the mailing list file is the only file in the
  383. MAILLISTS directory, then the listname does not have to be given on the command
  384. line."
  385.         exit 0
  386.         ;;
  387.     q)
  388.         Quiet=true
  389.         ;;
  390.     [ardARDcnpsif])
  391.         if [ -n "$act" ]; then
  392.         print -u2 "$lngname: Multiple actions specified ($act"\
  393. " and $opt).  Use -h for help.  Aborting."
  394.         exit 1
  395.         fi
  396.         act=$opt
  397.         ;;
  398.     [ul])
  399.         if [ -n "$Mod" ]; then
  400.         print -u2 "$lngname: Multiple modifiers specified ($Mod"\
  401. " and $opt).  Use -h for help.  Aborting."
  402.         exit 1
  403.         fi
  404.         Mod=$opt
  405.         ;;
  406.     :) 
  407.         print -r -u2 -- \
  408.         "$name: Option '$OPTARG' requires a value.  Use -h for help."
  409.         exit 1
  410.         ;;
  411.     ?) 
  412.         print -u2 "$lngname: $OPTARG: bad option.  Use -h for help."
  413.         exit 1
  414.         ;;
  415.     esac
  416.     done
  417.  
  418.     # remove args that were options
  419.     let OPTIND=OPTIND-1
  420.     shift $OPTIND
  421.  
  422.     case "$act" in
  423.     [ardARD])    # Adding or removing users
  424.     ReadUsers=false
  425.     ReadLists=false
  426.     ReadInput=false
  427.     case "$Mod" in
  428.     u)
  429.         ReadUsers=true
  430.         ReadInput=true
  431.         ArgsReq=list
  432.         ArgsRead=user
  433.         MinArgs=1
  434.         ;;
  435.     l)
  436.         ReadLists=true
  437.         ReadInput=true
  438.         ArgsReq=user
  439.         ArgsRead=list
  440.         MinArgs=1
  441.         ;;
  442.     "")
  443.         [[ $act = [ARD] ]] &&
  444.         ArgsReq="list and user" || ArgsReq="user and list"
  445.         MinArgs=2
  446.         ;;
  447.     esac
  448.     if [ $# -lt MinArgs ]; then
  449.         print -u2 \
  450.     "$lngname: must give $ArgsReq names(s) with -$act.  Use -h for help."
  451.         exit 1
  452.     fi
  453.     OFS=$IFS
  454.     IFS=,
  455.     if $ReadInput; then
  456.         [ -t 0 -a -t 1 ] && print "Enter $ArgsRead names; end with ^D"
  457.         set -A ReadArgs $(<&0)
  458.         if [ "${#ReadArgs[*]}" -eq 0 ]; then
  459.         print -u2 \
  460.     "$lngname: must give at least one $ArgsRead name.  Use -h for help."
  461.         exit 1
  462.         fi
  463.     fi
  464.     if [[ $act = [ard] ]]; then # arg1 is users, others are lists
  465.         $ReadUsers || {
  466.         Users=$1
  467.         shift
  468.         }
  469.         if $ReadLists && [ $# -gt 1 ]; then
  470.         print -u2 "Too many arguments given with -$act$Mod"
  471.         exit 1
  472.         fi
  473.         set -A ListArr "$@"
  474.         if $ReadUsers; then
  475.         set -A Args -- "${ReadArgs[@]}"
  476.         else
  477.         set -A Args $Users
  478.         fi
  479.     else            # arg1 is lists, others are users
  480.         $ReadLists || {
  481.         if [ $# -eq 0 ]; then
  482.             print -u2 \
  483.         "$lngname: must give list names(s) with -$act.  Use -h for help."
  484.             exit 1
  485.         fi
  486.         ListList=$1
  487.         shift
  488.         }
  489.         if $ReadUsers && [ $# -gt 1 ]; then
  490.         print -u2 "Too many arguments given with -$act$Mod"
  491.         exit 1
  492.         fi
  493.         set -A Args "$@"
  494.         if $ReadLists; then
  495.         set -A ListArr -- "${ReadArgs[@]}"
  496.         else
  497.         set -A ListArr $ListList
  498.         fi
  499.     fi
  500.     IFS=$OFS
  501.     # Set arg to DoUser to be 0 if removing, 1 if adding
  502.     [[ $act = [rRdD] ]]
  503.     ListCmd="DoUsers $?"
  504.     set -- "${ListArr[@]}"
  505.     ;;
  506.     n)
  507.     ListCmd=ChangeHosts
  508.     if [ $# -lt 2 ]; then
  509.         print -u2 \
  510.     "$lngname: must give old & new host names with -$act.  Use -h for help."
  511.         exit 1
  512.     fi
  513.     Args[1]=$1
  514.     Args[2]=$2
  515.     shift 2
  516.     [ $# -eq 0 ] && set -- %
  517.     ;;
  518.     c)
  519.     ListCmd=ChangeList
  520.     case "$Mod" in
  521.     u)
  522.         # Save args, since any set -A overwrites them
  523.         set -A lNames -- "$@"
  524.         [ -t 0 -a -t 1 ] &&
  525.         print "Enter pairs of user names; end with ^D"
  526.         while read line; do
  527.         set -- $line
  528.         case $# in
  529.         0)
  530.             ;;
  531.         2)
  532.             set -A Args "${Args[@]}" "$1" "$2"
  533.             ;;
  534.         *)
  535.             print -u2 \
  536.         "$lngname: Need exactly two names on each line.  Ignored: $line"
  537.             ;;
  538.         esac
  539.         done
  540.         set -- "${lNames[@]}"
  541.         ;;
  542.     "")
  543.         if [ $# -lt 2 ]; then
  544.         print -u2 \
  545. "$lngname: must give -u or old & new user names with -$act.  Use -h for help."
  546.         exit 1
  547.         fi
  548.         Args[1]=$1
  549.         Args[2]=$2
  550.         shift 2
  551.         ;;
  552.     *)
  553.         print -u2 \
  554.     "$lngname: Invalid modifier '$Mod' given with -$act.  Use -h for help."
  555.         exit 1
  556.         ;;
  557.     esac
  558.     [ $# -eq 0 ] && set -- %
  559.     ;;
  560.     s)
  561.     if [ $# -lt 1 ]; then
  562.         print -u2 \
  563.         "$lngname: must give user pattern -$act.  Use -h for help."
  564.         exit 1
  565.     fi
  566.     Args[1]=$1
  567.     shift
  568.     ListCmd=findPat
  569.     [ $# -eq 0 ] && set -- %
  570.     ;;
  571.     p)
  572.     ListCmd=printLists
  573.     [ $# -eq 0 ] && set -- %
  574.     ;;
  575.     i)
  576.     ListCmd=initialize
  577.     ;;
  578.     f)
  579.     ListCmd=fix
  580.     ;;
  581.     esac
  582.     # List dir might not exist if doing initialize.
  583.     # But if the dir does exist, should cd to it even if doing initialize
  584.     # before expanding list names.
  585.     if [ ! -d "$MAILLISTS" ]; then
  586.     if [ $ListCmd = initialize ]; then
  587.         print "Making list directory '$MAILLISTS'."
  588.         mkdir -e -p -m 711 "$MAILLISTS" || {
  589.         print -u2 "Could not create mailing list directory '$MAILLISTS'"
  590.         exit 1
  591.         }
  592.     else
  593.         print -u2 \
  594.     "Mailing list directory '$MAILLISTS' does not exist or is not a directory."
  595.         exit 1
  596.     fi
  597.     fi
  598.     OPWD=${PWD%/}
  599.     cd -- "$MAILLISTS" || {
  600.     print -u2 "Could not cd to mailing list directory '$MAILLISTS'."
  601.     exit 1
  602.     }
  603.     # At this point, positional arguments are list names.
  604.  
  605.     if [[ $# -eq 0 && "$opt" = [fpadrc] && $NoML = false ]]; then
  606.     set -- *
  607.     OnlyOne=true
  608.     else
  609.     OnlyOne=false
  610.     set -A Lists -- "$@"
  611.     typeset -i j=0 i=${#Lists[*]}
  612.     while [ j -lt i ]; do
  613.         case "${Lists[j]}" in
  614.         %) Lists[j]='*';;
  615.         */*) Lists[j]="$OPWD/${Lists[j]#./}";;
  616.         esac
  617.         let j+=1
  618.     done
  619.  
  620.     # Expand filename patterns
  621.     eval set -- "${Lists[@]}"
  622.     fi
  623.  
  624.     unset Lists[*]
  625.     i=0
  626.     for list; do
  627.     if [[ "$list" != *- ]]; then
  628.         Lists[i]=$list
  629.         let i+=1
  630.     fi
  631.     done
  632.     if [ i -eq 0 ]; then
  633.     print -u2 "$lngname: No lists."
  634.     exit 1
  635.     elif $OnlyOne; then
  636.     if [ i -eq 1 ]; then
  637.         print "Acting on mailing list ${Lists[*]}"
  638.     else
  639.         print -u2 "$lngname: No list names given on command line,
  640. and too many list files in MAILLISTS directory."
  641.         exit 1
  642.     fi
  643.     fi
  644.     typeset -i numLists=${#Lists[@]}    # for use by printLists
  645.     for list in "${Lists[@]}"; do
  646.     $ListCmd "$list" "${Args[@]}" || {
  647.         [[ "$list" != /* ]] && list="$PWD/$list"
  648.         print -u2 "Failure occured in processing $list"
  649.     }
  650.     done
  651. }
  652.  
  653. ### Start of main program
  654.  
  655. NoML=false
  656. Quiet=false
  657.  
  658. if [ -z "$MAILLISTS" ]; then
  659.     rc=$HOME/$rcfile
  660.     [ -r $rc -a -f $rc ] && . $rc
  661.     if [ -z "$MAILLISTS" ]; then
  662.     MAILLISTS=$HOME
  663.     NoML=true
  664.     fi
  665. fi
  666.  
  667. lngname=${0##*/}
  668.  
  669. Usage="Usage: $lngname [-aAcfhilpqrsRu] arg[,arg...] arg ..."
  670.  
  671. if [[ $# -eq 0 || "$1" != -?* ]]; then
  672.     print -u2 "$Usage\nUse -h for help."
  673.     exit
  674. fi
  675.  
  676. doArgs "$@"
  677.